package main

// 计算 mag patent 的 颠覆度，这里使用全部 links

import (
	"context"
	"log"
	"mag/internal/database"
	"sync"
	"time"

	"github.com/emirpasic/gods/sets/hashset"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type PageTypeInfo struct {
	Id  int64   `bson:"_id"`
	In  []int64 `bson:"in"`
	Out []int64 `bson:"out"`
	// S    []string `bson:"S"`
	Type string `bson:"doc_type"`
	Year int    `bson:"year"`
}

// "doc_type" : "Patent",

var (
	yearMap = make(map[int64]int)
)

func main() {
	// 初始化容器
	log.Println("开始导入")
	// rangInMap := make(map[int64]*hashset.Set)
	inMap := make(map[int64]*hashset.Set)
	outMap := make(map[int64]*hashset.Set)
	patentArray := []int64{}

	ctx := context.Background()
	client := database.InitMongo()
	collection := client.Database("mag2020").Collection("pageinfo_all")

	cur, err := collection.Find(ctx, bson.M{})
	if err != nil {
		log.Fatal(err)
	}
	defer cur.Close(ctx)

	for cur.Next(ctx) {

		var result PageTypeInfo
		err := cur.Decode(&result)
		if err != nil {
			log.Fatal(err)
		}

		if result.Type == "Patent" {
			patentArray = append(patentArray, result.Id)
		}

		// 加入 year
		yearMap[result.Id] = result.Year

		// 开始过滤一遍
		if len(result.Out) == 0 && len(result.In) == 0 {
			continue
		}

		// 初始化 out data
		if _, ok := outMap[result.Id]; !ok {
			outMap[result.Id] = hashset.New()
		}
		if _, ok := inMap[result.Id]; !ok {
			inMap[result.Id] = hashset.New()
		}

		for _, item := range result.Out {
			outMap[result.Id].Add(item)
		}

		for _, item := range result.In {
			inMap[result.Id].Add(item)
		}
	}

	log.Println("数据导入完毕，开始计算 D")
	// 开始计算 disruption
	outchan := calDisruption(outMap, inMap, patentArray)
	handleOutChan(outchan, "mag")

	log.Println("over")
	time.Sleep(99999)
}

type disruptionItem struct {
	ID   int64   `bson:"_id"`
	D    float64 `bson:"d"`
	I    int64   `bson:"i"`
	J    int64   `bson:"j"`
	K    int64   `bson:"k"`
	Year int     `bson:"y"`
}

func (c *disruptionItem) CalD() {
	c.D = (float64(c.I) - float64(c.J)) / (float64(c.I) + float64(c.J) + float64(c.K))
}

type mapdata struct {
	Key int64
	Set *hashset.Set
}

func calDisruption(outMap, inMap map[int64]*hashset.Set, patentArray []int64) chan *disruptionItem {
	outChan := make(chan *disruptionItem, 10000)
	go func() {
		var handleChan = make(chan mapdata, 10000)
		go func() {
			for _, key := range patentArray {
				var item = mapdata{key, outMap[key]}
				handleChan <- item

			}
			// for key, outSet := range outMap {
			// 	var item = mapdata{key, outSet}
			// 	handleChan <- item
			// }
			close(handleChan)
		}()
		wg := sync.WaitGroup{}
		wg.Add(20)
		for i := 0; i < 20; i++ {
			go func() {
				for item := range handleChan {
					key := item.Key
					outSet := item.Set
					retData := disruptionItem{ID: key}
					retData.Year = yearMap[key]

					// 获取 linksin set, 如果没有了，就跳过
					inSet := inMap[key]
					if inSet == nil || outSet.Size() == 0 {
						continue
					}

					outInSet := hashset.New()

					for _, item := range outSet.Values() {
						if outinset, ok := inMap[item.(int64)]; ok {
							outInSet.Add(outinset.Values()...)
						}
					}

					// 计算颠覆度参数
					for _, nodeID := range inSet.Values() {
						nodeIDOutSet := outMap[nodeID.(int64)]
						if Is_intersection(nodeIDOutSet, outSet) {
							retData.J += 1
						} else {
							retData.I += 1
						}
					}
					// 排除自身节点
					outInSet.Remove(key)
					retData.K = Cal_uniq_count(outInSet, inSet)
					retData.CalD()
					outChan <- &retData
				}
				wg.Done()
			}()
		}
		wg.Wait()
		close(outChan)
	}()
	return outChan
}

func handleOutChan(outchan chan *disruptionItem, collectionName string) {

	collection := database.InitMongo().Database("patent").Collection(collectionName)

	opts := options.BulkWrite().SetOrdered(false)
	models := []mongo.WriteModel{}
	i := 0
	for item := range outchan {
		i += 1
		models = append(models, mongo.NewInsertOneModel().SetDocument(item))
		if i%10000 == 0 {
			retTmp, err := collection.BulkWrite(context.Background(), models, opts)
			if err != nil {
				log.Println("bulk upsert fail", err)
				log.Printf("%+v", retTmp)
			}
			models = []mongo.WriteModel{}
		}
	}
	if len(models) > 0 {
		retTmp, err := collection.BulkWrite(context.Background(), models, opts)
		if err != nil {
			log.Println("bulk upsert fail", err)
			log.Printf("%+v", retTmp)
		}
	}
}

// 计算是否有交集
func Is_intersection(a, b *hashset.Set) bool {
	for _, v := range a.Values() {
		if b.Contains(v.(int64)) {
			return true
		}
	}
	return false
}

// 计算差
func Cal_uniq_count(a, b *hashset.Set) (count int64) {
	for _, v := range a.Values() {
		if !b.Contains(v.(int64)) {
			count += 1
		}
	}
	return
}
